﻿using System.Collections.Generic;
using System;

namespace XNAGameEngine.DataStructures
{
    /// <summary>
    /// A simple stack based implementation of a finite state machine. The top of the stack is always the current state.
    /// You may change to a new state by the ChangeState() methods which will swap out the top state with the new on passed in, or
    /// by PushState() which will push a new state onto the stack.
    /// </summary>
    /// <typeparam name="managerType">The class inheriting this FSM. Must inherit the managerInterfaceType passed in as the third type parameter</typeparam>
    /// <typeparam name="stateType">The type of state this FSM will manage. Must inherit FSMState</typeparam>
    /// <typeparam name="managerInterfaceType">The interface to interact with this FSM, may pass in the same type specified by the first parameter</typeparam>
    public abstract class FSM<managerType, stateType, managerInterfaceType>
        where stateType : FSMState<managerInterfaceType, stateType> 
        where managerType : managerInterfaceType
    {
        #region local fields
        
        /// <summary>
        /// The stack used to manager the FSM. The top of the stack will always be the current state.
        /// </summary>
        private Stack<FSMState<managerInterfaceType, stateType>> _stateStack;
        
        #endregion

        #region constructors

        /// <summary>
        /// Constructor, creates the stack
        /// </summary>
        FSM(){
            _stateStack = new Stack<FSMState<managerInterfaceType, stateType>>();
        }

        #endregion

        #region methods

        /// <summary>
        /// Change to a new state while keeping the current state in tact.
        /// </summary>
        /// <param name="newState">state to change to</param>
        public virtual void PushState(FSMState<managerInterfaceType, stateType> newState)
        {
            newState.SetStateManager = this;
            _stateStack.Push(newState);
            TopState().Resume();
        }

        /// <summary>
        /// Change to a new state while destroying the current state.
        /// </summary>
        /// <param name="newState">state to change to</param>
        public virtual void ChangeState(FSMState<managerInterfaceType, stateType> newState)
        {
            ChangeState(newState, false);
        }

        /// <summary>
        /// Change to a new state while destroying the current state. Optional ability to clear all other states on the stack
        /// </summary>
        /// <param name="newState">new state to change to</param>
        /// <param name="clearStack">should the stack be cleared before changing</param>
        public virtual void ChangeState(FSMState<managerInterfaceType, stateType> newState, bool clearStack)
        {
            if (clearStack) 
                ClearStack();
            else PopState();

            PushState(newState);
        }

        /// <summary>
        /// Remove the top state from the stack and resume a previous stat.
        /// </summary>
        /// <returns>the state object which is removed from the stack</returns>
        public virtual stateType PopState()
        {
            TopState().Pause();
            stateType temp = (stateType)_stateStack.Pop();
            TopState().Resume();
            return temp;
        }

        /// <summary>
        /// Returns the current state object
        /// </summary>
        /// <returns>the top state on the stack</returns>
        public virtual stateType TopState()
        {
            if (_stateStack.Count == 0)
                throw new Exception("No states on the stack");

            return (stateType)_stateStack.Peek();
        }

        /// <summary>
        /// Clear the entire state stack.
        /// </summary>
        private void ClearStack()
        {
            while (_stateStack.Count > 0)
                _stateStack.Pop();
        }

        #endregion      
    }
}
